package com.tomtop.core.redis;

import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.RedisSystemException;
import org.springframework.data.redis.connection.DefaultStringRedisConnection;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisConnectionUtils;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import com.google.common.collect.Maps;
import com.tomtop._const.Const;
import com.tomtop.core.exception.GlobalException;
import com.tomtop.core.exception.GlobalExceptionStatus;

public class EnableTransactionIntegerRedisTemplate extends RedisTemplate<String, Integer> {

	static Logger Logger = LoggerFactory.getLogger(EnableTransactionIntegerRedisTemplate.class);

	public EnableTransactionIntegerRedisTemplate() {
		RedisSerializer<String> stringSerializer = new StringRedisSerializer();
		RedisSerializer<Integer> integerSerializer = new RedisSerializer<Integer>() {
			public Integer deserialize(byte[] bytes) {
				return bytes == null ? null : Integer.parseInt(new String(bytes));
			}

			public byte[] serialize(Integer integer) {
				return integer == null ? null : String.valueOf(integer).getBytes();
			}
		};
		setKeySerializer(stringSerializer);
		setValueSerializer(integerSerializer);
		setHashKeySerializer(stringSerializer);
		setHashValueSerializer(integerSerializer);
		setEnableTransactionSupport(true);
	}

	public EnableTransactionIntegerRedisTemplate(RedisConnectionFactory connectionFactory) {
		this();
		setConnectionFactory(connectionFactory);
		afterPropertiesSet();
	}

	protected RedisConnection preProcessConnection(RedisConnection connection, boolean existingConnection) {
		return new DefaultStringRedisConnection(connection);
	}

	public void multiUpdate(Map<String, Integer> oasm) {
		for (int i = 0; i < Const.REDIS_TRANSACTION_RETRY_NUMBER; i++)
			try {
				this.multiUpdateExec(oasm);
				break;
			} catch (RedisSystemException e) {
				Logger.info("redis提交事务失败");
				if (i > Const.REDIS_TRANSACTION_RETRY_NUMBER) {
					Logger.error("修改redis失败，redis提交事务超过{}次仍然未完成", Const.REDIS_TRANSACTION_RETRY_NUMBER, e);
					throw new GlobalException(GlobalExceptionStatus.UPDATE_REDIS_FAIL);
				}
				Logger.info("已重试{}次，在次重试提交", i);
			} finally {
				this.unbind();
			}
	}

	protected void multiUpdateExec(Map<String, Integer> oasm) {
		Map<String, Integer> poasm = Maps.newHashMap();
		this.watch(oasm.keySet());
		oasm.keySet().forEach((rediskey) -> {
			int qty = oasm.get(rediskey);
			if (this.hasKey(rediskey)) {
				Integer rqty = this.opsForValue().get(rediskey);
				if (rqty != null)
					qty += rqty;
				if (qty < 0) {
					Logger.error("redis 值修改后不可以小于0");
					throw new GlobalException(GlobalExceptionStatus.UPDATE_REDIS_FAIL);
				}
			}
			poasm.put(rediskey, qty);
		});
		this.multi();
		this.opsForValue().multiSet(poasm);
		this.exec();
	}

	public void unbind() {
		RedisConnectionUtils.unbindConnection(getConnectionFactory());
	}
}
